using JESAI.Logs;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using JESAI.Core.Extensions;
using JESAI.Core.Util.Extensions.Strings;

namespace JESAI.Logs.MLog
{
    internal class LoggerHelper
    {
        internal static bool hasInitQueue = false;
        /// <summary>
        /// 单例
        /// </summary>
        public static readonly ILogger Instance = new InternalLogger<object>("");

        internal static BlockingCollection<LogContext> messageQueue = new BlockingCollection<LogContext>();
    }
    internal class InternalLogger<T> : ILogger, ILogger<T>
    {
        internal InternalLogger(string categoryName)
        {
            this.CategoryName = categoryName;
            InitQueue();
        }

        /// <summary>
        /// 日志类别
        /// </summary>
        public string CategoryName { get; set; }
        private static void InitQueue()
        {
            if (!LoggerHelper.hasInitQueue)
            {
                lock (typeof(ILogger))
                {
                    if (!LoggerHelper.hasInitQueue)
                    {
                        LoggerHelper.hasInitQueue = true;
                        Task.Factory.StartNew(() =>
                        {
                            while (true)
                            {
                                try
                                {
                                    var ctx = LoggerHelper.messageQueue.Take();
                                    LogFile(ctx);
                                }
                                catch (Exception ex)
                                {
                                    LogInternal(ex);
                                }
                            }
                        }, TaskCreationOptions.LongRunning);
                    }
                }
            }
        }

        /// <summary>
        /// 记录日志
        /// </summary>
        /// <param name="categoryName"></param>
        /// <param name="level"></param>
        /// <param name="message"></param>
        /// <param name="exception"></param>
        public void Log(string categoryName, LoggerLevel level, string message, Exception exception = null)
        {
            var ctx = new LogContext
            {
                CategoryName = categoryName,
                Level = level,
                Message = message,
                Time = DateTime.Now,
                Exception = exception
            };
            LoggerFactory.LogAction?.Invoke(ctx);
            if (LoggerFactory.LogAction == null || LoggerFactory.EnableDefaultOutPut) LoggerHelper.messageQueue.TryAdd(ctx);
        }

        public void LogTrace(string message) => Log(CategoryName, LoggerLevel.Trace, message, null);
        public void LogDebug(string message) => Log(CategoryName, LoggerLevel.Debug, message, null);
        public void LogInformation(string message) => Log(CategoryName, LoggerLevel.Information, message, null);
        public void LogWarning(string message) => Log(CategoryName, LoggerLevel.Warning, message, null);
        public void LogError(string message) => Log(CategoryName, LoggerLevel.Error, message, null);
        public void LogError(Exception exception) => Log(CategoryName, LoggerLevel.Error, null, exception);
        public void LogError(Exception exception, string message) => Log(CategoryName, LoggerLevel.Error, message, exception);
        public void LogCritical(string message) => Log(CategoryName, LoggerLevel.Critical, message, null);
        public void LogCritical(Exception exception) => Log(CategoryName, LoggerLevel.Critical, null, exception);
        public void LogCritical(Exception exception, string message) => Log(CategoryName, LoggerLevel.Critical, message, exception);

        private static string internalLogFileDir = "";
        static InternalLogger()
        {
            internalLogFileDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ".logs");
            if (!Directory.Exists(internalLogFileDir)) Directory.CreateDirectory(internalLogFileDir);
        }

        private static void LogInternal(Exception ex)
        {
            var filePath = Path.Combine(internalLogFileDir, "internal.log");
            var msg = $"{DateTime.Now.ToGlobalStampString()}    Message: {ex?.Message} StackTrace: {ex?.StackTrace}\r\nInnerException.Message: {ex?.InnerException?.Message}  InnerException.StackTrace: {ex?.InnerException?.StackTrace}";
            File.AppendAllLines(filePath, new string[] { msg });
        }

        private static void LogFile(LogContext logContext)
        {
            //是否可输出
            if (LoggerFactory.GetCanOutFunc != null)
            {
                if (!LoggerFactory.GetCanOutFunc(logContext)) return;
            }
            //构建默认的日志输出格式
            var sb = new StringBuilder();
            sb.Append(logContext.Time.ToGlobalStamp2String());
            sb.Append($" [{logContext.Level.ToDescription()}]");
            if (logContext.CategoryName.IsNotNullOrEmptyOrWhiteSpace()) sb.Append($" {logContext.CategoryName}:");
            sb.Append($" {logContext.Message}");
            if (logContext.Exception != null)
            {
                var exp = logContext.Exception;
                var deep = 0;
                do
                {
                    if (deep == 0)
                    {
                        sb.Append("\r\n").Append($" {exp.GetType().GetClassFullName()}: {exp.Message}");
                        sb.Append("\r\n").Append($"{exp.StackTrace}");
                    }
                    else
                    {
                        sb.Append("\r\n").Append($" inner{deep}: {exp.GetType().GetClassFullName()}: {exp.Message}");
                        sb.Append("\r\n").Append($"{exp.StackTrace}");
                    }

                    if (deep < 16)
                    {
                        exp = exp.InnerException;
                        deep++;
                    }
                    else
                    {
                        if (exp.InnerException != null)
                        {
                            sb.Append("\r\n").Append($"{" ".Repeat(deep + 1)}太多InnerException,已省略输出...");
                        }
                        break;
                    }
                } while (exp != null);
            }
            var msg = sb.ToString();
            //输出格式
            if (LoggerFactory.GetOutFormatFunc != null)
            {
                msg = LoggerFactory.GetOutFormatFunc(new LoggerFactory.SetOutFormatArg
                {
                    LogContext = logContext,
                    PreMessage = msg
                });
            }
            //自动计算输出文件路径
            var filePath = calcLogPath(null);
            var errorFilePath = string.Empty;
            if (logContext.Level.To<int>() > LoggerLevel.Warning.To<int>())
            {
                errorFilePath = calcLogPath("fail");
            }
            //自定义输出文件路径
            if (LoggerFactory.GetOutFileFunc != null)
            {
                var tmp = LoggerFactory.GetOutFileFunc.Invoke(new LoggerFactory.SetOutFileArg
                {
                    BaseDir = LoggerFactory.BaseDir,
                    LogContext = logContext,
                    PreLogFile = filePath,
                    PreErrorLogFile = errorFilePath
                });
                if (tmp.EnumerableIsNotNullOrEmpty())
                {
                    for (int i = 0; i < tmp.Length; i++)
                    {
                        var file = tmp[i];
                        if (file.IsNullOrEmptyOrWhiteSpace()) continue;
                        var dir = Path.GetDirectoryName(file);
                        if (!Directory.Exists(dir)) Directory.CreateDirectory(dir);
                        File.AppendAllLines(file, new[] { msg });
                    }
                }
            }
            else
            {
                File.AppendAllLines(filePath, new[] { msg });
                if (errorFilePath.IsNotNullOrEmptyOrWhiteSpace()) File.AppendAllLines(errorFilePath, new[] { msg });
            }

        }

        private static int maxLength = 1024 * 1024 * 10;
        private static string calcLogPath(string prefix)
        {
            var dirPath = LoggerFactory.BaseDir;
            if (!Directory.Exists(dirPath)) Directory.CreateDirectory(dirPath);
            var fileName = $"commonlog-{DateTime.Now.ToString("yyyyMMdd")}";
            if (prefix.IsNotNullOrEmptyOrWhiteSpace()) fileName = $"commonlog-{prefix}-{DateTime.Now.ToString("yyyyMMdd")}";
            var fileNameWithExt = fileName + ".txt";
            var files = new DirectoryInfo(dirPath).GetFiles(fileName + "*.txt").Select(i => i.Name).ToList();
            var index = calcIndex(files, fileName);
            var fullPath = Path.Combine(dirPath, fileNameWithExt);
            if (index > 0)
            {
                //滚动文件
                fullPath = Path.Combine(dirPath, fileName + ".r" + index + ".txt");
            }
            if (File.Exists(fullPath))
            {
                var fileInfo = new FileInfo(fullPath);
                if (fileInfo.Length > maxLength)
                {
                    index++;
                    fullPath = Path.Combine(dirPath, fileName + ".r" + index + ".txt");
                }
            }
            return fullPath;
        }

        /// <summary>
        /// 返回当前文件列表的最新序号
        /// </summary>
        /// <param name="files"></param>
        /// <param name="fileName"></param>
        /// <returns></returns>
        private static int calcIndex(List<string> files, string fileName)
        {
            if (files.Count == 0) return 0;
            if (files.Count == 1 && files[0] == fileName) return 0;
            var index = 0;
            foreach (var file in files)
            {
                if (!file.StartsWith(fileName)) continue;
                if (file == fileName + ".txt") continue;
                var list = file.SplitAndTrimTo<string>(".");
                if (list.Count != 3) continue;
                var t = list[1].Replace("r", "").ToWithDefault<int>(0);
                index = Math.Max(index, t);
            }
            return index;
        }
    }
}
